// CompressCall.cpp

#include "StdAfx.h"

#include "Common/String/StringConvert.h"

#include "Common/FileSystem/FileDir.h"
#include "../atlobjutil.h"
#include "Common/System/Process.h"
#include "Common/System/Threading/Thread.h"

#include "Common/App/ProgramLocation.h"
#include "Common/Registry/RegistryUtils.h"

#include "CompressCall.h"

#include <stdlib.h>


HWND l_hWnd = NULL;

namespace Common
{
using namespace System;
namespace Archive
{

static LPCTSTR kShowDialogSwitch = _T(" -ad");
static LPCTSTR kEmailSwitch = _T(" -seml.");
static LPCTSTR kIncludeSwitch = _T(" -i");
static LPCTSTR kArchiveTypeSwitch = _T(" -t");
static LPCTSTR kArcIncludeSwitches = _T(" -an -ai");
static LPCTSTR kStopSwitchParsing = _T(" --");
static LPCTSTR kLargePagesDisable = _T(" -slp-");

CString GetQuotedString(const CString &s)
{
	return CString(_T('\"')) + s + CString(_T('\"'));
}
static void ErrorMessage(LPCWSTR message)
{
	if(NULL == l_hWnd)
	{
		l_hWnd = ::GetTopWindow(NULL);
	}
	MessageBox(l_hWnd, message, _T("7-Zip"), MB_ICONERROR | MB_OK);
}

static void ErrorMessageHRESULT(HRESULT res, LPCWSTR s = NULL)
{
	CString s2 = HResultToMessage(res);
	if (s)
	{
		s2 += _T('\n');
		s2 += s;
	}
	ErrorMessage(s2);
}

static HRESULT MyCreateProcess(LPCWSTR imageName, const CString &params,
							   LPCWSTR curDir, bool waitFinish,
							   Threading::CEvent* event)
{
	CProcess process;
	DWORD res = process.Create(imageName, params, curDir);
	if (res != 0)
	{
		ErrorMessageHRESULT(res, imageName);
		return res;
	}
	if (waitFinish)
	{
		process.WaitForObject();
	}
	else if (event != NULL)
	{
		HANDLE handles[] = { process, *event };
		::WaitForMultipleObjects(sizeof(handles) / sizeof(handles[0]), handles, FALSE, INFINITE);
	}
	return S_OK;
}

static void AddLagePagesSwitch(CString &params)
{
	if (!Registry::ReadLockMemoryEnable())
	{
		params += kLargePagesDisable;
	}
}

static CString Get7zGuiPath()
{
	CString path;
	App::GetProgramFolderPath(path);
	return path + _T("7zG.exe");
}

class CRandNameGenerator
{
public:
	CRandNameGenerator()
	{
		srand(::GetTickCount());
	}
	CString GenerateName()
	{
		TCHAR temp[16];
		_ultot((UINT32)rand(), temp, 10);
		return temp;
	}
};

static HRESULT CreateMap(const CStringVector &names,
						 CFileMapping &fileMapping, Threading::CEvent &event,
						 CString &params)
{
	UINT32 totalSize = 1;
	for (size_t i = 0; i < names.size(); i++)
	{
		totalSize += (names[i].GetLength() + 1);
	}
	totalSize *= sizeof(wchar_t);

	CRandNameGenerator random;

	CString mappingName;
	for (;;)
	{
		mappingName = _T("7zMap") + random.GenerateName();

		HANDLE hFileHandle = fileMapping.CreateFileMapping(INVALID_HANDLE_VALUE, PAGE_READWRITE, 0, totalSize,
							 String::GetSystemString(mappingName));
		DWORD res(0);
		if (NULL == hFileHandle)
		{
			res = ::GetLastError();
		}
		if (NULL != hFileHandle && res == 0)
		{
			break;
		}
		if (res != ERROR_ALREADY_EXISTS)
		{
			return res;
		}
		fileMapping.CloseHandle();
	}

	CString eventName;
	for (;;)
	{
		eventName = _T("7zEvent") + random.GenerateName();
		DWORD res = event.Create(FALSE, FALSE, String::GetSystemString(eventName));// CreateWiCthName(false, String::GetSystemString(eventName));
		if (event.IsOpen() && res == 0)
		{
			break;
		}
		if (res != ERROR_ALREADY_EXISTS)
		{
			return res;
		}
		event.CloseHandle();
	}

	params += _T('#');
	params += mappingName;
	params += _T(':');
	TCHAR temp[16];
	_ultot(totalSize, temp, 10);
	params += temp;

	params += _T(':');
	params += eventName;

	LPVOID data = fileMapping.MapViewOfFile(FILE_MAP_WRITE, 0, 0, totalSize);
	if (data == NULL)
	{
		return E_FAIL;
	}

	wchar_t* cur = (wchar_t*)data;
	*cur++ = 0;
	for (size_t i = 0; i < names.size(); i++)
	{
		const CString &s = names[i];
		int len = s.GetLength() + 1;
		memcpy(cur, (const wchar_t*)s, len * sizeof(wchar_t));
		cur += len;
	}
	fileMapping.UnmapViewOfFile();
	return S_OK;
}

HRESULT CompressFiles(
	const CString &arcPathPrefix,
	const CString &arcName,
	const CString &arcType,
	const CStringVector &names,
	bool email, bool showDialog, bool waitFinish)
{
	try
	{
		CString params = _T('a');

		CFileMapping fileMapping;
		//Synchronization::CManualResetEvent event;
		Threading::CEvent event;
		params += kIncludeSwitch;
		HRESULT hRes(CreateMap(names, fileMapping, event, params));
		if(S_OK != hRes)
		{
			return hRes;
		}

		if (!arcType.IsEmpty())
		{
			params += kArchiveTypeSwitch;
			params += arcType;
		}

		if (email)
		{
			params += kEmailSwitch;
		}

		if (showDialog)
		{
			params += kShowDialogSwitch;
		}

		AddLagePagesSwitch(params);

		params += kStopSwitchParsing;
		params += _T(' ');

		params += GetQuotedString(arcName);

		return MyCreateProcess(Get7zGuiPath(), params,
							   (arcPathPrefix.IsEmpty() ? 0 : (LPCWSTR)arcPathPrefix), waitFinish, &event);
	}
	catch(...)
	{
		ErrorMessageHRESULT(E_FAIL);
		return E_FAIL;
	}
}

static HRESULT ExtractGroupCommand(const CStringVector &arcPaths, CString &params)
{
	AddLagePagesSwitch(params);
	params += kArcIncludeSwitches;
	CFileMapping fileMapping;
	//Synchronization::CManualResetEvent event;
	Threading::CEvent event;
	HRESULT hRes(CreateMap(arcPaths, fileMapping, event, params));
	if(S_OK != hRes)
	{
		return hRes;
	}
	return MyCreateProcess(Get7zGuiPath(), params, 0, false, &event);
}

HRESULT ExtractArchives(const CStringVector &arcPaths, const CString &outFolder, bool showDialog)
{
	try
	{
		CString params = _T('x');
		if (!outFolder.IsEmpty())
		{
			params += _T(" -o");
			params += GetQuotedString(outFolder);
		}
		if (showDialog)
		{
			params += kShowDialogSwitch;
		}
		return ExtractGroupCommand(arcPaths, params);
	}
	catch(...)
	{
		ErrorMessageHRESULT(E_FAIL);
		return E_FAIL;
	}
}

HRESULT TestArchives(const CStringVector &arcPaths)
{
	try
	{
		CString params = _T('t');
		return ExtractGroupCommand(arcPaths, params);
	}
	catch(...)
	{
		ErrorMessageHRESULT(E_FAIL);
		return E_FAIL;
	}
}

HRESULT Benchmark()
{
	try
	{
		return MyCreateProcess(Get7zGuiPath(), _T('b'), 0, false, NULL);
	}
	catch(...)
	{
		ErrorMessageHRESULT(E_FAIL);
		return E_FAIL;
	}
}

}
}